#include "common.h"


using namespace Scenic::Renderer;


VertexStore::VertexStore(int type, Context *context, Affine2 *tm, ClipArea *clip)
{
	init(type, context, tm, clip);
}

void VertexStore::init(int type, Context *context, Affine2 *tm, ClipArea *clip)
{
	setType(type);
	this->context = context;
	this->tm = tm;
	numVertices = 0;
	if(!clip)
		clip = context->clip;
	clipRect.left = clip->x;
	clipRect.top = clip->y;
	clipRect.right = clip->x + clip->width;
	clipRect.bottom = clip->y + clip->height;
	default_tu2 = default_tv2 = 0.0;
}

void VertexStore::setType(int type)
{
	this->type = type;
	vertexSize = 4 * 4 + 8 * type;
}

void VertexStore::setDefaultTex2(double tu2, double tv2)
{
	default_tu2 = tu2;
	default_tv2 = tv2;
}

int VertexStore::addVertex(double x, double y, int diffuse)
{
	VERTEX_INFO *v = block.addPointer(1);

	v->index = -1;
	if(tm)
	{
		v->p[0] = tm->m00 * x + tm->m01 * y + tm->dx;
		v->p[1] = tm->m10 * x + tm->m11 * y + tm->dy;
	}
	else
	{
		v->p[0] = x;
		v->p[1] = y;
	}
	v->diffuse = diffuse;

	return block.size - 1;
}

int VertexStore::addVertex(double x, double y, int diffuse, double tu1, double tv1)
{
	VERTEX_INFO *v = block.addPointer(1);

	v->index = -1;
	if(tm)
	{
		v->p[0] = tm->m00 * x + tm->m01 * y + tm->dx;
		v->p[1] = tm->m10 * x + tm->m11 * y + tm->dy;
	}
	else
	{
		v->p[0] = x;
		v->p[1] = y;
	}
	v->diffuse = diffuse;
	v->tex[0][0] = tu1;
	v->tex[0][1] = tv1;
	v->tex[1][0] = default_tu2;
	v->tex[1][1] = default_tv2;

	return block.size - 1;
}

int VertexStore::addVertex(double x, double y, int diffuse, double tu1, double tv1, double tu2, double tv2)
{
	VERTEX_INFO *v = block.addPointer(1);

	v->index = -1;
	if(tm)
	{
		v->p[0] = tm->m00 * x + tm->m01 * y + tm->dx;
		v->p[1] = tm->m10 * x + tm->m11 * y + tm->dy;
	}
	else
	{
		v->p[0] = x;
		v->p[1] = y;
	}
	v->diffuse = diffuse;
	v->tex[0][0] = tu1;
	v->tex[0][1] = tv1;
	v->tex[1][0] = tu2;
	v->tex[1][1] = tv2;

	return block.size - 1;
}

void VertexStore::addTriangle(int a, int b, int c)
{
	int vi[3] = {a, b, c};
	ClipArea *clip = context->clip;
	double left = clip->x;
	double right = clip->x + clip->width;
	double top = clip->y;
	double bottom = clip->y + clip->height;
	int numOutside[4] = {0, 0, 0, 0};

	for(int i = 0; i < 3; i++)
	{
		VERTEX_INFO *v = &block.data[vi[i]];

		if(v->p[0] < left)
			numOutside[0]++;
		else if(v->p[0] > right)
			numOutside[1]++;
		if(v->p[1] < top)
			numOutside[2]++;
		else if(v->p[1] > bottom)
			numOutside[3]++;
	}
	if(numOutside[0] == 3 || numOutside[1] == 3 || numOutside[2] == 3 || numOutside[3] == 3)
		return;
	if(numOutside[0] > 0 || numOutside[1] > 0 || numOutside[2] > 0 || numOutside[3] > 0)
		clipTriangle(vi, 0);
	else
		processTriangle(vi);
}

void VertexStore::endBlock()
{
	block.size = 0;
}

void VertexStore::draw()
{
#ifdef DIRECTX
	int fvf = D3DFVF_XYZ | D3DFVF_DIFFUSE;
	
	if(type == VSF_TEX0)
		fvf |= D3DFVF_TEX0;
	else if(type == VSF_TEX1)
		fvf |= D3DFVF_TEX1;
	else if(type == VSF_TEX2)
		fvf |= D3DFVF_TEX2;

	context->device->getDevice()->SetFVF(fvf);
	if(Device::getCaps().d3dcaps.MaxVertexIndex > 0xffff && 
		Device::getCaps().d3dcaps.MaxVertexIndex >= (unsigned int)numVertices)
	{
		context->device->getDevice()->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 
			0, numVertices,
			indices.size / 3,
			indices.data, D3DFMT_INDEX32,
			vertices.data, vertexSize); 
	}
	else
	{
		char *temp = new char[vertexSize * 300];
		int i;

		for(i = 0; i < indices.size; i += 300)
		{
			int l = min(300, indices.size - i);
			int j;

			for(j = 0; j < l; j++)
			{
				memcpy(temp + j * vertexSize, 
					vertices.data + indices.data[i + j] * vertexSize,
					vertexSize);
			}
			context->device->getDevice()->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 
				l / 3, 
				temp,
				vertexSize);
		}

		delete temp;
	}
#endif
#ifdef OPT_OPENGL
	glVertexPointer(3, GL_FLOAT, vertexSize, vertices.data);
	glEnableClientState(GL_VERTEX_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, vertexSize, vertices.data + 4 * 3);
	glEnableClientState(GL_COLOR_ARRAY);

	if(type >= VSF_TEX1)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glClientActiveTextureARB(GL_TEXTURE0_ARB);
		glTexCoordPointer(2, GL_FLOAT, vertexSize, vertices.data + 4 * 3 + 4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	if(type >= VSF_TEX2)
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glClientActiveTextureARB(GL_TEXTURE1_ARB);
		glTexCoordPointer(2, GL_FLOAT, vertexSize, vertices.data + 4 * 3 + 4 + 4 * 2);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	context->device->makeCurrent();
	glDrawElements(GL_TRIANGLES, indices.size, GL_UNSIGNED_INT, indices.data);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE1_ARB);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
#endif
}

void VertexStore::empty()
{
	vertices.size = 0;
	numVertices = 0;
	indices.size = 0;
	block.size = 0;
}

static int interpolateColor(int c1, int c2, double a)
{
	int ia1 = (int)(a * 256.0);
	int ia2 = 256 - ia1;

	return ((((c1) & 0xff) * ia1 + ((c2) & 0xff) * ia2) >> 8)
		| ((((c1 >> 8) & 0xff) * ia1 + ((c2 >> 8) & 0xff) * ia2) >> 8 << 8)
		| ((((c1 >> 16) & 0xff) * ia1 + ((c2 >> 16) & 0xff) * ia2) >> 8 << 16)
		| ((((c1 >> 24) & 0xff) * ia1 + ((c2 >> 24) & 0xff) * ia2) >> 8 << 24);
}

void VertexStore::processTriangle(int *vi)
{
	ClipArea *clip = context->clip;
	int i;

	for(i = 0; i < 3; i++)
	{
		VERTEX_INFO *v = &block.data[vi[i]];

		if(v->index == -1)
		{
			COMMON_VERTEX *cv = (COMMON_VERTEX*)vertices.addPointer(vertexSize);

			v->index = numVertices++;
			cv->x = (float)v->p[0];
			cv->y = (float)v->p[1];
			cv->z = 0.0f;
			cv->diffuse = v->diffuse;
			if(type >= VSF_TEX1)
			{
				cv->tex[0][0] = (float)v->tex[0][0];
				cv->tex[0][1] = (float)v->tex[0][1];
			}
			if(type >= VSF_TEX2)
			{
				cv->tex[1][0] = (float)v->tex[1][0];
				cv->tex[1][1] = (float)v->tex[1][1];
			}

			cv->x -= clip->x;
			cv->y -= clip->y;
		}
		indices.add(v->index);
	}
}

void VertexStore::clipTriangle(int *vi, int stage)
{
	if(stage == 4)
	{
		processTriangle(vi);
		return;
	}

	static int axises[4] = {0, 0, 1, 1};
	static int dirs[4] = {1, -1, 1, -1};
	double borders[4] = {clipRect.left, clipRect.right, clipRect.top, clipRect.bottom};

	int i;
	int axis = axises[stage];
	int axis2 = 1 - axis;
	double border = borders[stage];
	int dir = dirs[stage];

	int in[3], out[3], middle[2];
	int nIn = 0, nOut = 0;

//	printf("clip %d %d %lf", axis, dir, border);

	for(i = 0; i < 3; i++)
	{
		VERTEX_INFO *v = &block.data[vi[i]];

		if(dir * v->p[axis] < dir * border)
			out[nOut++] = vi[i];
		else
			in[nIn++] = vi[i];
	}
	if(nOut == 0)
	{
		clipTriangle(vi, stage + 1);
	}
	else if(nOut == 1)
	{
		for(i = 0; i < 2; i++)
		{
			VERTEX_INFO *nv = block.addPointer(1);
			VERTEX_INFO *v1 = &block.data[out[0]];
			VERTEX_INFO *v2 = &block.data[in[i]];
			double a3 = v2->p[axis] - v1->p[axis];
			double a1 = (v2->p[axis] - border) / a3;
			double a2 = (border - v1->p[axis]) / a3;

			nv->index = -1;
			nv->p[axis] = border;
			nv->p[axis2] = v1->p[axis2] * a1 + v2->p[axis2] * a2;
			nv->diffuse = interpolateColor(v1->diffuse, v2->diffuse, a1);
			if(type >= VSF_TEX1)
			{
				nv->tex[0][0] = v1->tex[0][0] * a1 + v2->tex[0][0] * a2;
				nv->tex[0][1] = v1->tex[0][1] * a1 + v2->tex[0][1] * a2;
			}
			if(type >= VSF_TEX2)
			{
				nv->tex[1][0] = v1->tex[1][0] * a1 + v2->tex[1][0] * a2;
				nv->tex[1][1] = v1->tex[1][1] * a1 + v2->tex[1][1] * a2;
			}
			middle[i] = block.size - 1;
		}
		int ni[3];

		ni[0] = in[0];
		ni[1] = middle[1];
		ni[2] = middle[0];
		clipTriangle(ni, stage + 1);
		ni[0] = in[0];
		ni[1] = in[1];
		ni[2] = middle[1];
		clipTriangle(ni, stage + 1);
	}
	else if(nOut == 2)
	{
		for(i = 0; i < 2; i++)
		{
			VERTEX_INFO *nv = block.addPointer(1);
			VERTEX_INFO *v1 = &block.data[out[i]];
			VERTEX_INFO *v2 = &block.data[in[0]];
			double a3 = v2->p[axis] - v1->p[axis];
			double a1 = (v2->p[axis] - border) / a3;
			double a2 = (border - v1->p[axis]) / a3;

			nv->index = -1;
			nv->p[axis] = border;
			nv->p[axis2] = v1->p[axis2] * a1 + v2->p[axis2] * a2;
			nv->diffuse = interpolateColor(v1->diffuse, v2->diffuse, a1);
			if(type >= VSF_TEX1)
			{
				nv->tex[0][0] = v1->tex[0][0] * a1 + v2->tex[0][0] * a2;
				nv->tex[0][1] = v1->tex[0][1] * a1 + v2->tex[0][1] * a2;
			}
			if(type >= VSF_TEX2)
			{
				nv->tex[1][0] = v1->tex[1][0] * a1 + v2->tex[1][0] * a2;
				nv->tex[1][1] = v1->tex[1][1] * a1 + v2->tex[1][1] * a2;
			}
			middle[i] = block.size - 1;
		}
		int ni[3];

		ni[0] = in[0];
		ni[1] = middle[1];
		ni[2] = middle[0];
		clipTriangle(ni, stage + 1);
	}
}

